﻿//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Licensed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////
#include "StructureEditProfileUtils.h"
#include "IModelLine.h"
#include "UiCommonDialog.h"
#include "ISelection.h"
#include "IPolygon.h"
#include "IGraphicsNodeReference.h"
#include "IStructureSlopeLine.h"
#include "IElementParameters.h"
#include "IParameterDefinitionLibrary.h"
#include "GbmpBuiltInParameterDefinitionsNew.h"
#include "IGraphicsCurve3d.h"
#include "EnLoopsState.h"
#include "IPlane.h"
#include "AlgorithmProject.h"
#include "AlgorithmCurveOperate.h"
#include "AlgorithmMinimalDistance.h"
#include "IHighlights.h"
#include "EnableCompileWarning_The_LAST_IncludeInCpp.h"

using namespace gcmp;

namespace
{
    enum class EnModelLineState :int32_t
    {
        MODELLINE_NOT_CLOSED = 0,    //不封闭
        MODELLINE_SELF_INTERSECT = 1,    //自交
        MODELLINE_INTERSECT = 2,    //相交
        MODELLINE_NO_CURVE = 3,    //没有曲线
        MODELLINE_MULTIPROLILE = 4,    //(不支持）存在多个轮廓
        MODELLINE_INNERLOOP = 5,    //(不支持）存在内轮廓
        MODELLINE_VALID = 6     //合法
    };

    /////////////////////////////////////////////////////////////////////////
    /*使用样例
    std::vector<ModelLine*> allModelLines = IModelLine::GetAllModelLines(doc);
    std::vector<gcmp::OwnerPtr<gcmp::IPolygon>>   polygonList;
    std::vector<ModelLine*>                        errorModelLines;
    gcmp::Coordinate3d coord = GmModelViewUtil::GetWorkPlaneCoordinate(modelView);
    ModelLineHelper::EnModelLineState eState = ModelLineHelper::GenPolygons(allModelLines, coord, polygonList, errorModelLines);
    */
    EnModelLineState GenPolygons(
        const std::vector<IModelLine*>                   allModelLines,
        const gcmp::Coordinate3d&                       coord,
        std::vector<gcmp::OwnerPtr<gcmp::IPolygon>>&   polygonList,
        std::vector<IModelLine*>&                        errorModelLines,
        bool supportMultiProfile/* = false*/,
        bool supportInnerLoop/* = false*/)
    {
        EnModelLineState eState = EnModelLineState::MODELLINE_NO_CURVE;
        if (allModelLines.size() == 0)
            return eState;

        std::vector<OwnerPtr<ICurve2d>> opCurve2dlist;
        FOR_EACH(curModelLine, allModelLines)
        {
            DBG_WARN_AND_RETURN_UNLESS(curModelLine, EnModelLineState::MODELLINE_NO_CURVE, L"模型线指针为空",L"GDMPLab",L"2024-03-30");
            OwnerPtr<IGraphicsCurve3d> opCurve = IGraphicsCurve3d::Create(curModelLine->GetGeometryCurve().get());
            OwnerPtr<IPlane> opPlane = IPlane::Create(coord.GetOrigin(), coord.GetX(), coord.GetY());
            OwnerPtr<ICurve2d> pGCurve = AlgorithmProject::Project(opCurve->GetGeometry(), opPlane.get());
            DBG_WARN_AND_RETURN_UNLESS(pGCurve, EnModelLineState::MODELLINE_NO_CURVE, L"转换为2d曲线失败",L"GDMPLab",L"2024-03-30");
            opCurve2dlist.push_back(TransferOwnership(pGCurve));
        }

        std::vector<const gcmp::ICurve2d*> curve2dlist;
        FOR_EACH(item, opCurve2dlist)
        {
            curve2dlist.push_back(item.get());
        }

        std::vector<const ICurve2d*> errorCurves;
        std::map<std::tuple<int, int, int>, int> polygonLoopCoedgeIndexToOriginCrvIndexMap;
        gcmp::EnLoopsState loopsState = gcmp::AlgorithmCurveOperate::Curve2dlistToPolygonLists(curve2dlist, polygonList, polygonLoopCoedgeIndexToOriginCrvIndexMap, gcmp::Constants::LENGTH_EPS, errorCurves);
        if (loopsState != EnLoopsState::LOOPVALID)
        {
            errorModelLines.clear();
            FOR_EACH_INDEX(index1, errorCurves)
            {
                FOR_EACH_INDEX(index2, curve2dlist)
                {
                    if (errorCurves[index1] == curve2dlist[index2])
                    {
                        errorModelLines.push_back(allModelLines[index2]);
                    }
                }
            }
        }

        switch (loopsState)
        {
        case gcmp::EnLoopsState::LOOPVALID:
        {
            eState = EnModelLineState::MODELLINE_VALID;
            if (!supportMultiProfile && polygonList.size() > 1)
            {
                eState = EnModelLineState::MODELLINE_MULTIPROLILE;

                errorModelLines.clear();
                FOR_EACH(modelLine, allModelLines)
                {
                    errorModelLines.push_back(modelLine);
                }
            }

            if (!supportInnerLoop)
            {
                int idxPoly = 0;
                FOR_EACH(profile, polygonList)
                {
                    int loopCount = profile->GetPolyCurveCount();
                    if (loopCount <= 1)
                        continue;

                    int idxLoop = 0;
                    eState = EnModelLineState::MODELLINE_INNERLOOP;
                    errorModelLines.clear();
                    while (++idxLoop < loopCount)
                    {
                        gcmp::OwnerPtr<IPolyCurve> opLoop = profile->GetPolyCurve(idxLoop);
                        for (int i = 0; i < opLoop->GetCurveCount(); ++i)
                        {
                            auto it = polygonLoopCoedgeIndexToOriginCrvIndexMap.find(std::tuple<int, int, int>(idxPoly, idxLoop, i));
                            if (it != polygonLoopCoedgeIndexToOriginCrvIndexMap.end())
                            {
                                int idxCurve = it->second;
                                DBG_WARN_AND_CONTINUE_UNLESS(idxCurve < allModelLines.size(), L"模型线索引范围有误!",L"GDMPLab",L"2024-03-30");
                                errorModelLines.push_back(allModelLines[idxCurve]);
                            }
                        }
                    }
                    ++idxPoly;
                }
            }
        }
        break;
        case gcmp::EnLoopsState::LOOPNOTCLOSED:
        {
            eState = EnModelLineState::MODELLINE_NOT_CLOSED;
        }
        break;
        case gcmp::EnLoopsState::LOOPSELFINTERSECT:
        {
            eState = EnModelLineState::MODELLINE_SELF_INTERSECT;
        }
        break;
        case gcmp::EnLoopsState::LOOPINTERSECT:
        {
            eState = EnModelLineState::MODELLINE_INTERSECT;
        }
        break;
        case gcmp::EnLoopsState::LOOPNOCURVE:
        {
            eState = EnModelLineState::MODELLINE_NO_CURVE;
        }
        break;
        default:
            break;
        }

        return eState;
    }
}
// 检查轮廓线是否合法，并做提示
bool StructureEditProfileUtils::CheckForValidLoops(const std::vector<IModelLine*>& modelLines, const gcmp::Coordinate3d& coord, bool supportMultiProfile, bool supportInnerLoop)
{
    DBG_WARN_AND_RETURN_FALSE_UNLESS(modelLines.empty() == false, L"modelLines不能为空",L"GDMPLab",L"2024-03-30");
    IDocument* pDoc = modelLines[0]->GetOwnerElement()->GetDocument();

    std::vector<OwnerPtr<IPolygon>> polygonList;
    std::vector<IModelLine*> errorModelLines;
    //TODO: 后续再改
    EnModelLineState eState = GenPolygons(modelLines, coord, polygonList, errorModelLines, supportMultiProfile, supportInnerLoop);
    if (eState != EnModelLineState::MODELLINE_VALID)
    {
        ISelection::Get()->Clear(pDoc);
        IHighlights::Get()->Clear();

        GraphicsNodeReferenceOwnerPtrSet newSelections;
        std::vector<OwnerPtr<IGraphicsNodeReference>> newHighlights;

        //将错误曲线加入选择集
        FOR_EACH(curve, errorModelLines)
        {
            OwnerPtr<IGraphicsNodeReference> extrusionGNode = IGraphicsNodeReference::Create(curve->GetOwnerElement()->GetElementId(), GraphicsNodeId());
            newSelections.insert(extrusionGNode->Clone());
            newHighlights.push_back(TransferOwnership(extrusionGNode));
        }

        // 集中设置，减少由选择集、高亮集变更引起的性能问题
        ISelection::Get()->AddGroupGraphicsNodeReference(pDoc, newSelections);
        IHighlights::Get()->AddGraphicsNodeReferences(newHighlights);

        switch (eState)
        {
        case EnModelLineState::MODELLINE_NOT_CLOSED:
            UiCommonDialog::ShowMiniMessageBox(GBMP_TR(L"警告"), GBMP_TR(L"轮廓线不闭合，请查看视图中高亮的线!"), (int)UiCommonDialog::ButtonType::OK, (int)UiCommonDialog::PositionType::BottomRight);
            break;
        case EnModelLineState::MODELLINE_NO_CURVE:
            UiCommonDialog::ShowMiniMessageBox(GBMP_TR(L"警告"), GBMP_TR(L"轮廓线为空，无法生成体!"), (int)UiCommonDialog::ButtonType::OK, (int)UiCommonDialog::PositionType::BottomRight);
            break;
        case EnModelLineState::MODELLINE_SELF_INTERSECT:
            UiCommonDialog::ShowMiniMessageBox(GBMP_TR(L"警告"), GBMP_TR(L"轮廓线不能自交，请查看视图中高亮的线！"), (int)UiCommonDialog::ButtonType::OK, (int)UiCommonDialog::PositionType::BottomRight);
            break;
        case EnModelLineState::MODELLINE_INTERSECT:
            UiCommonDialog::ShowMiniMessageBox(GBMP_TR(L"警告"), GBMP_TR(L"轮廓线不能相交，请查看视图中高亮的线！"), (int)UiCommonDialog::ButtonType::OK, (int)UiCommonDialog::PositionType::BottomRight);
            break;
        case EnModelLineState::MODELLINE_INNERLOOP:
            UiCommonDialog::ShowMiniMessageBox(GBMP_TR(L"警告"), GBMP_TR(L"不支持内轮廓，请查看视图中高亮的线！"), (int)UiCommonDialog::ButtonType::OK, (int)UiCommonDialog::PositionType::BottomRight);
            break;
        case EnModelLineState::MODELLINE_MULTIPROLILE:
            UiCommonDialog::ShowMiniMessageBox(GBMP_TR(L"警告"), GBMP_TR(L"不支持多个轮廓边界，请查看视图中高亮的线！"), (int)UiCommonDialog::ButtonType::OK, (int)UiCommonDialog::PositionType::BottomRight);
            break;
        default:
            DBG_WARN(L"没有对应的错误提示信息，请添加",L"GDMPLab",L"2024-03-30");
            break;
        }
        return false;
    }
    return true;
}

bool gcmp::StructureEditProfileUtils::CheckSlopeLineAndProfileCurveRelationshipValidity(
    std::vector<IStructureSlopeLine*>& slopeLines,
    std::vector<IModelLine*>& profileCurves,
    std::map<IStructureSlopeLine*, IModelLine*>& slopeLineAndProfileCurveMap)
{
    DBG_WARN_AND_RETURN_FALSE_UNLESS(profileCurves.size() > 0, L"传入的轮廓为空",L"GDMPLab",L"2024-03-30");

    std::vector<IModelLine*> profileCurvesUsed;
    for (int i = 0; i < slopeLines.size(); i++)
    {
        // 判断坡度线的起点是否在某轮廓线上
        bool isSlopeLineStartPointOnProfileCurve = false;
        Vector3d startPt = slopeLines[i]->GetStart();
        for (int j = 0; j < profileCurves.size(); j++)
        {
            Vector3d minimalDistancePointOnCurve;
            double pParamOnCurve = 0.0;
            double minimalDistance = 0.0;
            AlgorithmMinimalDistance::Calculate(profileCurves[j]->GetGeometryCurve().get(), startPt, minimalDistancePointOnCurve, true, &pParamOnCurve, minimalDistance);
            if ((minimalDistance + Constants::DOUBLE_EPS) < gcmp::Constants::DISTANCE_ITERATION_EPS)
            {
                // 同时在屋顶边界线上和屋顶坡段箭头上定义坡度，违反 Rule 4
                IElementParameters* pElementParameters = profileCurves[j]->GetOwnerElement()->GetElementParameters();
                OwnerPtr<IParameter> paramIsDefineSlope = pElementParameters->GetParameterByUid(PARAMETER_UID(RoofProfileCurveDefineSlopeBuiltInParameter));
                DBG_WARN_AND_RETURN_FALSE_UNLESS(IParameter::IsValidParameter(paramIsDefineSlope), L"paramIsDefineSlope参数无效",L"GDMPLab",L"2024-03-30");
                bool isDefineSlope = paramIsDefineSlope->GetValueAsBool();
                if (isDefineSlope)
                {
                    UiCommonDialog::ShowMessageBox(GBMP_TR(L"警告"), GBMP_TR(L"不能同时在屋顶轮廓线和屋顶坡度线上定义坡度！"), (int)UiCommonDialog::ButtonType::OK);
                    return false;
                }

                slopeLineAndProfileCurveMap.insert(std::pair<IStructureSlopeLine*, IModelLine*>(slopeLines[i], profileCurves[j]));
                profileCurvesUsed.push_back(profileCurves[j]);
                profileCurves.erase(profileCurves.begin() + j);
                isSlopeLineStartPointOnProfileCurve = true;
                break;
            }
        }

        // 同一条坡度线又在另一条轮廓线上，违反 Rule 2
        for (int j = 0; j < profileCurves.size(); j++)
        {
            Vector3d minimalDistancePointOnCurve;
            double pParamOnCurve = 0.0;
            double minimalDistance = 0.0;
            AlgorithmMinimalDistance::Calculate(profileCurves[j]->GetGeometryCurve().get(), startPt, minimalDistancePointOnCurve, true, &pParamOnCurve, minimalDistance);
            if ((minimalDistance + Constants::DOUBLE_EPS) < gcmp::Constants::DISTANCE_ITERATION_EPS)
            {
                UiCommonDialog::ShowMessageBox(GBMP_TR(L"警告"), GBMP_TR(L"一条坡度线只能在一条屋顶轮廓线上！"), (int)UiCommonDialog::ButtonType::OK);
                return false;
            }
        }


        if (!isSlopeLineStartPointOnProfileCurve)
        {
            // 坡度线的起点在已经标记使用过的轮廓线上，违反 Rule 3
            for (int k = 0; k < profileCurvesUsed.size(); k++)
            {
                Vector3d minimalDistancePointOnCurve;
                double pParamOnCurve = 0.0;
                double minimalDistance = 0.0;
                AlgorithmMinimalDistance::Calculate(profileCurvesUsed[k]->GetGeometryCurve().get(), startPt, minimalDistancePointOnCurve, true, &pParamOnCurve, minimalDistance);
                if ((minimalDistance + Constants::DOUBLE_EPS) < gcmp::Constants::DISTANCE_ITERATION_EPS)
                {
                    UiCommonDialog::ShowMessageBox(GBMP_TR(L"警告"), GBMP_TR(L"一条屋顶轮廓线最多对应一条坡度线！"), (int)UiCommonDialog::ButtonType::OK);
                    return false;
                }
            }

            // 坡度线的起点不在任何轮廓线上，违反 Rule 1
            UiCommonDialog::ShowMessageBox(GBMP_TR(L"警告"), GBMP_TR(L"所有坡度箭头的尾部都必须位于屋顶轮廓线上！"), (int)UiCommonDialog::ButtonType::OK);
            return false;
        }
    }

    return true;
}
